//
//  MHEncoded.m
//  JadeShop
//
//  Created by mac on 2019/7/19.
//  Copyright © 2019年 SZJ.test. All rights reserved.
//

#import "MHEncoded.h"
#import <VideoToolbox/VideoToolbox.h>
@interface MHEncoded()

@end


@implementation MHEncoded
{
    VTCompressionSessionRef _compressionSessionRef;
}

-(void)begin{

    /*
     allocator：内存分配器，填NULL为默认分配器
     width、height：视频帧像素的宽高，如果编码器不支持这个宽高的话可能会改变
     codecType：编码类型，枚举
     encoderSpecification：指定特定的编码器，填NULL的话由VideoToolBox自动选择
     sourceImageBufferAttributes：源像素缓冲区的属性，如果这个参数有值的话，VideoToolBox会创建一个缓冲池，不需要缓冲池可以设置为NULL
     compressedDataAllocator：压缩后数据的内存分配器，填NULL使用默认分配器
     outputCallback：视频编码后输出数据回调函数
     outputCallbackRefCon：回调函数中的自定义指针，我们通常传self，在回调函数中就可以拿到当前类的方法和属性了
     compressionSessionOut：编码器句柄，传入编码器的指针
     */
    
    
    //1、创建编码器
    OSStatus status = VTCompressionSessionCreate(NULL, YYScreenWidth(), YYScreenHeight(), kCMVideoCodecType_H264, NULL, NULL, NULL, encodeOutputDataCallback, (__bridge void *)self, &_compressionSessionRef);
    
    
    // 2设置编码器属性
    /*
     编码器创建完了，所有给编码器设置属性都是调用VTSessionSetProperty方法来实现。
     kVTCompressionPropertyKey_AverageBitRate：设置编码的平均码率，单位是bps，这不是一个硬性指标，设置的码率会上下浮动。VideoToolBox框架只支持ABR模式。H264有4种码率控制方法：
     
     
     
     CBR（Constant Bit Rate）是以恒定比特率方式进行编码，有Motion发生时，由于码率恒定，只能通过增大QP来减少码字大小，图像质量变差，当场景静止时，图像质量又变好，因此图像质量不稳定。这种算法优先考虑码率(带宽)。
     
     VBR（Variable Bit Rate）动态比特率，其码率可以随着图像的复杂程度的不同而变化，因此其编码效率比较高，Motion发生时，马赛克很少。码率控制算法根据图像内容确定使用的比特率，图像内容比较简单则分配较少的码率(似乎码字更合适)，图像内容复杂则分配较多的码字，这样既保证了质量，又兼顾带宽限制。这种算法优先考虑图像质量。
     *CVBR（Constrained VariableBit Rate）,这样翻译成中文就比较难听了，它是VBR的一种改进方法。但是Constrained又体现在什么地方呢？这种算法对应的Maximum bitRate恒定或者Average BitRate恒定。这种方法的兼顾了以上两种方法的优点：在图像内容静止时，节省带宽，有Motion发生时，利用前期节省的带宽来尽可能的提高图像质量，达到同时兼顾带宽和图像质量的目的。
     
     ABR (Average Bit Rate) 在一定的时间范围内达到设定的码率，但是局部码率峰值可以超过设定的码率，平均码率恒定。可以作为VBR和CBR的一种折中选择。
     
     
     kVTCompressionPropertyKey_ProfileLevel：设置H264编码的画质，H264有4种Profile：BP、EP、MP、HP
     
     BP(Baseline Profile)：基本画质。支持I/P 帧，只支持无交错（Progressive）和CAVLC；主要应用：可视电话，会议电视，和无线通讯等实时视频通讯领域
     EP(Extended profile)：进阶画质。支持I/P/B/SP/SI 帧，只支持无交错（Progressive）和CAVLC；
     MP(Main profile)：主流画质。提供I/P/B 帧，支持无交错（Progressive）和交错（Interlaced），也支持CAVLC 和CABAC 的支持；主要应用：数字广播电视和数字视频存储
     HP(High profile)：高级画质。在main Profile 的基础上增加了8×8内部预测、自定义量化、 无损视频编码和更多的YUV 格式；应用于广电和存储领域
     
     Level就多了，这里不一一列举，可参考h264 profile & level，iPhone上常用的方案如下：
     
     
     实时直播：
     低清Baseline Level 1.3
     标清Baseline Level 3
     半高清Baseline Level 3.1
     全高清Baseline Level 4.1
     存储媒体：
     低清 Main Level 1.3
     标清 Main Level 3
     半高清 Main Level 3.1
     全高清 Main Level 4.1
     高清存储：
     半高清 High Level 3.1
     全高清 High Level 4.1
     
     
     kVTCompressionPropertyKey_RealTime：设置是否实时编码输出
     kVTCompressionPropertyKey_AllowFrameReordering：配置是否产生B帧，High profile 支持 B 帧
     kVTCompressionPropertyKey_MaxKeyFrameInterval、kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration：配置I帧间隔
     设置完编码器的属性后，调用VTCompressionSessionPrepareToEncodeFrames准备编码

     */
    
    
     status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(512 * 1024));
    // 设置ProfileLevel为BP3.1
    status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_3_1);
    // 设置实时编码输出（避免延迟）
    status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
//    // 配置是否产生B帧
//    status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_AllowFrameReordering, self.videoEncodeParam.allowFrameReordering ? kCFBooleanTrue : kCFBooleanFalse);
//    // 配置最大I帧间隔  15帧 x 240秒 = 3600帧，也就是每隔3600帧编一个I帧
//    status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(self.videoEncodeParam.frameRate * self.videoEncodeParam.maxKeyFrameInterval));
//    // 配置I帧持续时间，240秒编一个I帧
//    status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, (__bridge CFTypeRef)@(self.videoEncodeParam.maxKeyFrameInterval));
    // 编码器准备编码
    status = VTCompressionSessionPrepareToEncodeFrames(_compressionSessionRef);
}
/**
 开始编码
 
 @return 结果
 */
- (BOOL)startVideoEncode
{
    if (NULL == _compressionSessionRef)
    {
        NSLog(@"VEVideoEncoder::调用顺序错误");
        return NO;
    }
    
    // 编码器准备编码
    OSStatus status = VTCompressionSessionPrepareToEncodeFrames(_compressionSessionRef);
    if (noErr != status)
    {
        NSLog(@"VEVideoEncoder::VTCompressionSessionPrepareToEncodeFrames failed status:%d", (int)status);
        return NO;
    }
    return YES;
}

/**
 停止编码
 
 @return 结果
 */
- (BOOL)stopVideoEncode
{
    if (NULL == _compressionSessionRef)
    {
        return NO;
    }
    
    OSStatus status = VTCompressionSessionCompleteFrames(_compressionSessionRef, kCMTimeInvalid);
    
    if (noErr != status)
    {
        NSLog(@"VEVideoEncoder::VTCompressionSessionCompleteFrames failed! status:%d", (int)status);
        return NO;
    }
    return YES;
}
/**
 编码过程中调整码率
 
 @param bitRate 码率
 @return 结果
 */
- (BOOL)adjustBitRate:(NSInteger)bitRate
{
    if (bitRate <= 0)
    {
        NSLog(@"VEVideoEncoder::adjustBitRate failed! bitRate <= 0");
        return NO;
    }
    OSStatus status = VTSessionSetProperty(_compressionSessionRef, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(bitRate));
    if (noErr != status)
    {
        NSLog(@"VEVideoEncoder::kVTCompressionPropertyKey_AverageBitRate failed status:%d", (int)status);
        return NO;
    }
    
    // 参考webRTC 限制最大码率不超过平均码率的1.5倍
    int64_t dataLimitBytesPerSecondValue =
    bitRate * 1.5 / 8;
    CFNumberRef bytesPerSecond = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitBytesPerSecondValue);
    int64_t oneSecondValue = 1;
    CFNumberRef oneSecond = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneSecondValue);
    const void* nums[2] = {bytesPerSecond, oneSecond};
    CFArrayRef dataRateLimits = CFArrayCreate(NULL, nums, 2, &kCFTypeArrayCallBacks);
    status = VTSessionSetProperty( _compressionSessionRef, kVTCompressionPropertyKey_DataRateLimits, dataRateLimits);
    if (noErr != status)
    {
        NSLog(@"VEVideoEncoder::kVTCompressionPropertyKey_DataRateLimits failed status:%d", (int)status);
        return NO;
    }
    return YES;
}

/**
 输入待编码数据
 
 @param imageBuffer 待编码数据
 @param forceKeyFrame 是否强制I帧
 @return 结果
 */

- (BOOL)encodeFrame:(CMSampleBufferRef)imageBuffer forceKeyFrame:(BOOL)forceKeyFrame{
    // 获取CVImageBufferRef
    CVImageBufferRef _imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(imageBuffer);
    // 设置是否为I帧
    NSDictionary *frameProperties = @{(__bridge NSString *)kVTEncodeFrameOptionKey_ForceKeyFrame: @(forceKeyFrame)};
    // 输入待编码数据
    OSStatus status = VTCompressionSessionEncodeFrame(_compressionSessionRef, _imageBuffer, kCMTimeInvalid, kCMTimeInvalid, (__bridge CFDictionaryRef)frameProperties, NULL, NULL);
     return status == noErr;
}

void encodeOutputDataCallback(void * CM_NULLABLE outputCallbackRefCon, void * CM_NULLABLE sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CM_NULLABLE CMSampleBufferRef sampleBuffer)
{
    if (noErr != status || nil == sampleBuffer)
    {
        NSLog(@"VEVideoEncoder::encodeOutputCallback Error : %d!", (int)status);
        return;
    }
    
    if (nil == outputCallbackRefCon)
    {
        return;
    }
    
    if (!CMSampleBufferDataIsReady(sampleBuffer))
    {
        return;
    }
    
    if (infoFlags & kVTEncodeInfo_FrameDropped)
    {
        NSLog(@"VEVideoEncoder::H264 encode dropped frame.");
        return;
    }
    
//    VEVideoEncoder *encoder = (__bridge VEVideoEncoder *)outputCallbackRefCon;
    const char header[] = "\x00\x00\x00\x01";
    size_t headerLen = (sizeof header) - 1;
    NSData *headerData = [NSData dataWithBytes:header length:headerLen];
    
    // 判断是否是关键帧
    bool isKeyFrame = !CFDictionaryContainsKey((CFDictionaryRef)CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0), (const void *)kCMSampleAttachmentKey_NotSync);
    
    if (isKeyFrame)
    {
        NSLog(@"VEVideoEncoder::编码了一个关键帧");
        CMFormatDescriptionRef formatDescriptionRef = CMSampleBufferGetFormatDescription(sampleBuffer);
        
        // 关键帧需要加上SPS、PPS信息
        size_t sParameterSetSize, sParameterSetCount;
        const uint8_t *sParameterSet;
        OSStatus spsStatus = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDescriptionRef, 0, &sParameterSet, &sParameterSetSize, &sParameterSetCount, 0);
        
        size_t pParameterSetSize, pParameterSetCount;
        const uint8_t *pParameterSet;
        OSStatus ppsStatus = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDescriptionRef, 1, &pParameterSet, &pParameterSetSize, &pParameterSetCount, 0);
        
        if (noErr == spsStatus && noErr == ppsStatus)
        {
            NSData *sps = [NSData dataWithBytes:sParameterSet length:sParameterSetSize];
            NSData *pps = [NSData dataWithBytes:pParameterSet length:pParameterSetSize];
            NSMutableData *spsData = [NSMutableData data];
            [spsData appendData:headerData];
            [spsData appendData:sps];
//            if ([encoder.delegate respondsToSelector:@selector(videoEncodeOutputDataCallback:isKeyFrame:)])
//            {
//                [encoder.delegate videoEncodeOutputDataCallback:spsData isKeyFrame:isKeyFrame];
//            }
            
            NSMutableData *ppsData = [NSMutableData data];
            [ppsData appendData:headerData];
            [ppsData appendData:pps];
            
//            if ([encoder.delegate respondsToSelector:@selector(videoEncodeOutputDataCallback:isKeyFrame:)])
//            {
//                [encoder.delegate videoEncodeOutputDataCallback:ppsData isKeyFrame:isKeyFrame];
//            }
        }
    }
    
    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    size_t length, totalLength;
    char *dataPointer;
    status = CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &dataPointer);
    if (noErr != status)
    {
        NSLog(@"VEVideoEncoder::CMBlockBufferGetDataPointer Error : %d!", (int)status);
        return;
    }
    
    size_t bufferOffset = 0;
    static const int avcHeaderLength = 4;
    while (bufferOffset < totalLength - avcHeaderLength)
    {
        // 读取 NAL 单元长度
        uint32_t nalUnitLength = 0;
        memcpy(&nalUnitLength, dataPointer + bufferOffset, avcHeaderLength);
        
        // 大端转小端
        nalUnitLength = CFSwapInt32BigToHost(nalUnitLength);
        
        NSData *frameData = [[NSData alloc] initWithBytes:(dataPointer + bufferOffset + avcHeaderLength) length:nalUnitLength];
        
        NSMutableData *outputFrameData = [NSMutableData data];
        [outputFrameData appendData:headerData];
        [outputFrameData appendData:frameData];
        
        bufferOffset += avcHeaderLength + nalUnitLength;
//
//        if ([encoder.delegate respondsToSelector:@selector(videoEncodeOutputDataCallback:isKeyFrame:)])
//        {
//            [encoder.delegate videoEncodeOutputDataCallback:outputFrameData isKeyFrame:isKeyFrame];
//        }
    }
    
}

@end
